Here we are going to implement an algorithm to find out the \(LU\)-Decomposition of a Matrix Efficiently. We will then use it to solve System of Equations. ### OBJECTIVE Implement the efficient version of Crout’s decomposition. Solve a system \(AX=b\) by forward and backward substitution.
Whenever we have a Matrix whose \(LU\)-Decomposition in possible, where \(L\) is lower triangular Matrix and \(U\) is upper Triangular Matrix with \(1\) across it’s diagonal. We can write \(A\) as a factorization \(A=LU\) and we can solve the Matrix System \[Ax = b\] \[LUx = b\] Now, we can solve 2 Systems Quickly by Forward and Backward Substitution to get the Solution that is \[Ly = b\ (Forward\ Substitution)\] \[Ux = y\ (Backward\ Substitution)\] Thus, we get the solution \(x\) to the original system.
# Computes the L matrix 
L <- function(A, j) {
  n <- nrow(A)
  tot <- rep(0, n-j+1)
  if (j > 1){
    for (k in 1:(j-1))
      tot <- tot + A[j:n, k] * A[k, j]
  }
  A[(j:n), j] <- A[(j:n), j] - tot
  return(A)
}
# Computes the U matrix
U <- function(A, i) {
  n <- nrow(A)
  tot <- rep(0, n-i)
  if (i > 1){
    for (k in 1:(i-1))
      tot <- tot + A[k, (i+1):n] * A[i, k]
  }
  if(A[i, i] == 0)
    stop("Singular Matrix Detected!")
  A[i, (i+1):n] <- (A[i, (i+1):n] - tot) / A[i, i]
  return(A)
}# Computes the LU Decomposition
Compute.LU <- function(A) {
  if(nrow(A) != ncol(A))
    stop("Matrix is not square!")
  
  n <- nrow(A)
  
  for(i in 1:n) {
    A <- L(A, i)
    if(i < n)
      A <- U(A, i)
  }
  
  L <- matrix(rep(0, n^2), n, n)
  U <- diag(n)
  
  for(j in 1:n)
    L[j:n,j] <- A[j:n, j]
  
  for(i in 1:(n-1))
    U[i, (i+1):n] <- A[i, (i+1):n]
  
  return(list("A" = A, "L"=L, "U"=U))
}Let’s Try it out on some Matrix.
A <- matrix(c(1,2,3,5,6,7,9,11,12), 3, 3)
kable(A)| 1 | 5 | 9 | 
| 2 | 6 | 11 | 
| 3 | 7 | 12 | 
The \(LU\)-Decomposition of the above Matrix.
decomp <- Compute.LU(A)
kable(decomp$A, caption = "Packed Matrix")| 1 | 5 | 9.00 | 
| 2 | -4 | 1.75 | 
| 3 | -8 | -1.00 | 
kable(decomp$L, caption = "L")| 1 | 0 | 0 | 
| 2 | -4 | 0 | 
| 3 | -8 | -1 | 
kable(decomp$U, caption = "U")| 1 | 5 | 9.00 | 
| 0 | 1 | 1.75 | 
| 0 | 0 | 1.00 | 
The Specification of \(L\) an \(U\) being lower and upper triangular matrix with \(U\) having diagonal elements as \(1\) is met. Let’s test if \(A = LU\).
kable(decomp$L %*% decomp$U, caption = "LU")| 1 | 5 | 9 | 
| 2 | 6 | 11 | 
| 3 | 7 | 12 | 
Indeed we get back the original matrix. - Now, we are going to use the above \(LU\)-Decomposition to obtain the solution for the System \(Ax=b\). The Following code implements this Forward Substitution and Backward Substitution as discussed earlier.
# Solve Linear System
Solve.LU <- function(A, b){
  LU <- Compute.LU(A)
  n <- nrow(A)
  
  # Forward Substitution
  y <- rep(0, n)
  if (LU$A[1,1] == 0) 
    stop("No Solution!")
  y[1] <- b[1] / LU$A[1,1] 
  for (i in 2:n) {
    if (LU$A[i,i] == 0)
      stop("No Solution!")
    y[i] <- (b[i] - sum(LU$A[i, 1:(i-1)]*y[1:(i-1)])) / LU$A[i,i] 
  }
  
  # Backward Substitution
  x <- rep(0, n)
  x[n] <- y[n]
  for (i in (n-1):1) {
    x[i] <- y[i] - sum(LU$A[i, (i+1):n]*x[(i+1):n])
  }
  
  return(list("LU"=LU, "x"=x))
}Let’s Test it on the Following System.
A <- matrix(c(2,4,-6,4,3,7,-10,6,0,2,0,4,0,0,1,5), 4, 4)
kable(A, caption = "A")| 2 | 3 | 0 | 0 | 
| 4 | 7 | 2 | 0 | 
| -6 | -10 | 0 | 1 | 
| 4 | 6 | 4 | 5 | 
kable(c(1,2,1,0), col.names = "b")| b | 
|---|
| 1 | 
| 2 | 
| 1 | 
| 0 | 
The solution is
sol <- Solve.LU(A, c(1,2,1,0))
kable(sol$x, caption = "Soln.")| x | 
|---|
| 11.500000 | 
| -7.333333 | 
| 3.666667 | 
| -3.333333 | 
Let’s check if \(Ax=b\).
kable(A %*% sol$x, col.names = "Ax")| Ax | 
|---|
| 1 | 
| 2 | 
| 1 | 
| 0 | 
Indeed we get \(b\) and hence it is a solution to the System.